using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;

using Atomic.Libraries;
using Atomic.Libraries.Mathematics;
using Atomic.Structures;
using Atomic.Structures.Symmetries;

#pragma warning disable // FIXME

namespace Atomic.Clusters
{
	[Serializable]
	[DebuggerDisplay("{Formula}")]
	public class MappedStructure : Structure
	{
		private IDictionary<Site, Site> mapping;

		private MappedStructure(Basis basis, Basis lattice, IEnumerable<Site> sites, Structure parentLattice, Structure expandedLattice, SpaceMatrix strain, IEnumerable<SpaceVector> translations, IDictionary<Site, Site> mapping)
			//: base(basis, lattice, sites)
			: base(null, new Site[0])
		{
			/*this.mapping = mapping;

			BaseLattice = parentLattice;
			ExpandedLattice = expandedLattice;
			Strain = strain;
			Translations = new ReadOnlyList<Vector3>(translations);*/
			throw new NotImplementedException();
		}

		public static MappedStructure MapStructure(Structure lattice, Structure structure)
		{
			/*CheckLattice(lattice);
			CheckStructure(structure);

			List<Site> expandedLatticeSites = new List<Site>();
			List<Site> mappedStructureSites = new List<Site>();
			Dictionary<Site, List<Vector3>> positions = new Dictionary<Site, List<Vector3>>();

			// Mapping of sites in the expanded lattice to sites created in the mapped structure.
			Dictionary<Site, Site> mapping = new Dictionary<Site, Site>();

			// Keep track of mapped sites to make sure that no site is unmapped.
			HashSet<Site> mappedSites = new HashSet<Site>();

			// Positions seem to be relative to the basis vectors of the structure.
			//Basis basis = structure.Basis;

			Matrix3 strain = null;
			Matrix3 inverseStrain = Matrix3.Identity();
			Basis expandedLatticeBasis = null;

			using (TempDirectory temp = new TempDirectory())
			{
				lattice.Write(new FileInfo(Path.Combine(temp.Directory.FullName, "lat.in")));
				structure.Write(new FileInfo(Path.Combine(temp.Directory.FullName, "str.out")));

				// Start corrdump process. Don't care about clusters here. IEEE 754 double-precision has up to 17 significant digits.
				Process p = new Process();
				p.StartInfo.RedirectStandardOutput = true;
				p.StartInfo.RedirectStandardError = true;
				p.StartInfo.UseShellExecute = false;
				p.StartInfo.FileName = "corrdump";
				p.StartInfo.Arguments = "-sig=17 -2=0.001";
				p.StartInfo.WorkingDirectory = temp.Directory.FullName;
				p.Start();
				p.WaitForExit();

				List<string> lines = new List<string>(File.ReadLines(Path.Combine(temp.Directory.FullName, "corrdump.log")));

				while (lines.Count > 0)
				{
					if (lines[0] == "strain=")
					{
						strain = AtomicEnvironment.ParseMatrix3(lines[1], lines[2], lines[3]);
						inverseStrain = Matrix3.Inverse(strain);
						//expandedLatticeBasis = new Basis(strain.A1, strain.A2, strain.A3) * structure.Basis; // FIXME transpose?
						expandedLatticeBasis = lattice.Basis;
						lines.RemoveRange(0, 4);
						continue;
					}

					string[] cols = lines[0].Split(' ');

					if (cols.Length == 16 && string.Join(" ", cols[1], cols[2]) == "atom at" && string.Join(" ", cols[6], cols[7], cols[8], cols[9], cols[10]) == "mapped onto lattice site at")
					{
						Atom atom = Atom.Parse(cols[0]);

						// Positions relative to lattice basis vectors.
						Vector3 structurePosition = AtomicEnvironment.ParseVector3(cols[3], cols[4], cols[5]);
						Vector3 latticePosition = AtomicEnvironment.ParseVector3(cols[11], cols[12], cols[13]);

						// Transform to absolute positions.
						structurePosition = inverseStrain * lattice.Basis.Map * structurePosition;
						latticePosition = lattice.Basis.Map * latticePosition;

						Site baseStructureSite = Identify(structure, atom, structure.Basis.InverseMap * structurePosition);
						Site baseLatticeSite = Identify(lattice, atom, lattice.Basis.InverseMap * latticePosition);

						// Positions already relative to the basis we'll use. FIXME ??

						ExpandedLatticeSite expandedLatticeSite = new ExpandedLatticeSite(baseLatticeSite, expandedLatticeBasis.InverseMap * latticePosition);
						expandedLatticeSites.Add(expandedLatticeSite);

						MappedSite mappedStructureSite = new MappedSite(baseStructureSite, expandedLatticeSite, baseStructureSite.Position);
						mappedStructureSites.Add(mappedStructureSite);

						mapping.Add(expandedLatticeSite, mappedStructureSite);
						mappedSites.Add(baseStructureSite);

						// Add all positions to determine translations in creation of the expanded lattice.
						if (!positions.ContainsKey(baseLatticeSite))
						{
							positions.Add(baseLatticeSite, new List<Vector3>());
						}
						positions[baseLatticeSite].Add(expandedLatticeSite.Position);
					}

					if (cols.Length == 7 && string.Join(" ", cols[1], cols[2], cols[3]) == "atom ignored at")
					{
						Atom atom = Atom.Parse(cols[0]);

						// FIXME Position transformation!!
						Vector3 structurePosition = AtomicEnvironment.ParseVector3(cols[4], cols[5], cols[6]);

						Site baseStructureSite = Identify(structure, atom, structurePosition);

						// Add???

						mappedSites.Add(baseStructureSite);


						//Site mappedSite = baseStructureSite;
						//mapping.Add(ss, ss); // FIXME

					}

					if (cols.Length == 5 && string.Join(cols[0], cols[1]) == "Vacancy at")
					{
						Atom atom = Atom.Vacancy;

						// FIXME Same!!!
						Vector3 structurePosition = AtomicEnvironment.ParseVector3(cols[4], cols[5], cols[6]);

						Site baseStructureSite = Identify(structure, atom, structurePosition);

						mappedSites.Add(baseStructureSite);

						//Site mappedSite = baseStructureSite;
						//mapping.Add(ss, ss); // FIXME
					}

					lines.RemoveAt(0);
				}
			}

			foreach (Site site in structure.Sites)
			{
				if (!mappedSites.Contains(site))
				{
					throw new Exception("Structure site not mapped.");
				}
			}

			// FIXME keep this check!!!
			//List<Site> sites = new List<Site>();
			//foreach (Site site in structure.Sites)
			//{
			//	if (!mapping.ContainsKey(site))
			//	{
			//		throw new AtomicException("Structure site not mapped.");
			//	}
			//
			//	sites.Add(mapping[site]);
			//}

			// Generate and verify translations.
			List<Vector3> translations = null;
			foreach (KeyValuePair<Site, List<Vector3>> position in positions)
			{
				List<Vector3> values = position.Value;
				if (translations == null)
				{
					translations = new List<Vector3>();
					foreach (Vector3 value in values)
					{
						translations.Add(values[0] - value);
					}
				}
				else
				{
					if (translations.Count != values.Count)
					{
						throw new Exception("Disagreement in number of translations.");
					}

					for (int i = 0; i < values.Count; i++)
					{
						Vector3 value = values[i] - values[0];
						if (Vector3.Norm(value - translations[i]) > 1.0e-6)
						{
							throw new Exception("Disagreement in translations.");
						}
					}
				}
			}

			// FIXME: Must be the same basis as the original lattice!!!!!!!!!!!!!! Otherwise cluster distances and other things will change. 
			// Use the same but unstrained basis vectors in the extended lattice as in the original structure.
			Structure expandedLattice = new Structure(structure.Basis, structure.Lattice, expandedLatticeSites);
			//Structure expandedLattice = new Structure(expandedLatticeBasis, lattice.Lattice, expandedLatticeSites);
			//Structure expandedLattice = new Structure(expandedLatticeBasis, new Basis(l1, l2, l3), expandedLatticeSites);

			// FIXME: Maybe use the same basis here also???
			// Use exactly the same basis vectors and lattice vectors in the mapped structure as in the original structure. FIXME ??
			return new MappedStructure(structure.Basis, structure.Lattice, mappedStructureSites, lattice, expandedLattice, strain, translations, mapping);
			//return new MappedStructure(structure.Basis, structure.Lattice, sites, inverseMap, translations[site0]);*/
			throw new NotImplementedException();
		}
	
		private static Site Identify(Structure structure, Atom atom, SpaceVector position)
		{
			/*// Only IEEE round-off errors expected in this identification.
			double tol = 1.0e-10;

			foreach (Site site in structure.Sites)
			{
				if (site.Atom == atom || site.Atom is PlaceholderAtom && ((PlaceholderAtom)site.Atom).Atoms.Contains(atom))
				{
					// Try solving for:
					// position == site.Position + a * structure.Lattice.A + ...
					Vector3 d = structure.Lattice.Decompose(position - site.Position);
					if (Math.Abs(d[0] - (int)Math.Round(d[0])) < tol && Math.Abs(d[1] - (int)Math.Round(d[1])) < tol && Math.Abs(d[2] - (int)Math.Round(d[2])) < tol)
					{
						return site;
					}
				}
			}

			throw new Exception("Couldn't identify site positions.");*/
			throw new NotImplementedException();
		}

		public ClusterClass MapCluster(ClusterClass cluster)
		{
			List<SpaceGroupSymmetryOperation> symmetries = new List<SpaceGroupSymmetryOperation>();
			foreach (SpaceGroupSymmetryOperation symmetry in cluster.symmetries)
			{
				foreach (SpaceVector translation in Translations)
				{
					symmetries.Add(new SpaceGroupSymmetryOperation(null, symmetry.Rotation, symmetry.Translation + translation));
				}
			}

			return new ClusterClass(ExpandedLattice, cluster.Points, cluster.MaxDistance, 0, symmetries);
		}

		//public IEnumerable<ClusterClass> MapClusters(params ClusterClass[] clusters)
		//{
		//	return MapClusters((IEnumerable<ClusterClass>)clusters);
		//}

		public IEnumerable<ClusterClass> MapClusters(IEnumerable<ClusterClass> clusters)
		{
			return new List<ClusterClass>(clusters).ConvertAll<ClusterClass>(cluster => MapCluster(cluster));
		}

		public ClusterExpansionTerm MapClusterExpansionTerm(ClusterExpansionTerm term)
		{
			return new ClusterExpansionTerm(MapCluster(term.Cluster), term.Coefficient);
		}

		public IEnumerable<ClusterExpansionTerm> MapClusterExpansionTerms(IEnumerable<ClusterExpansionTerm> terms)
		{
			return new List<ClusterExpansionTerm>(terms).ConvertAll<ClusterExpansionTerm>(term => MapClusterExpansionTerm(term));
		}

		/*public ClusterExpansion MapClusterExpansion(ClusterExpansion expansion)
		{
			return new ClusterExpansion(MapClusterExpansionTerms(expansion.Terms));
		}*/

		public double ClusterValue(ClusterClass cluster)
		{
			double x = 0.0;
			foreach (Cluster c in cluster.EquivalentClusters)
			{
				double y = 1.0;
				foreach (ClusterPoint p in c.Points)
				{
					Site s = mapping[p.LatticeSite];
					y *= p.ClusterFunction.Value(s.Atom);
				}
				x += y;
			}
			x /= cluster.Multiplicity;

			return x;
		}

		public double ClusterValue(params ClusterExpansionTerm[] terms)
		{
			double x = 0.0;
			foreach (ClusterExpansionTerm term in terms)
			{
				x += term.Coefficient * ClusterValue(term.Cluster);
			}

			return x;
		}

		private static void CheckLattice(Structure lattice)
		{
			foreach (Site site in lattice.Sites)
			{
				if (site.Atom is PlaceholderAtom)
				{
					return;
				}
			}

			throw new ArgumentException("Need at least one site with a placeholder in the lattice.");
		}

		private static void CheckStructure(Structure structure)
		{
			foreach (Site site in structure.Sites)
			{
				if (site.Atom is PlaceholderAtom)
				{
					throw new ArgumentException("Structure can't contain placeholder sites.");
				}
			}
		}

		/// <summary>
		/// The original structure.
		/// </summary>
		public Structure BaseStructure
		{
			get;
			private set;
		}

		/// <summary>
		/// The original lattice.
		/// </summary>
		public Structure BaseLattice
		{
			get;
			private set;
		}

		/// <summary>
		/// Lattice in the same representation as the mapped structure (i.e. same unstrained lattice vectors and
		/// site positions as the structure; but still with placeholders as the original lattice). The clusters
		/// are really defined on this lattice.
		/// </summary>
		public Structure ExpandedLattice
		{
			get;
			private set;
		}

		/*public Structure UnstrainedStructure
		{
			get;
			private set;
		}*/

		/// <summary>
		/// The strain of the structure relative the unrelaxed ideal lattice. Vectors in the structure is multiplied by the matrix
		/// to obtain ideal lattice positions.
		/// </summary>
		public SpaceMatrix Strain
		{
			get;
			private set;
		}

		public ReadOnlyList<SpaceVector> Translations
		{
			get;
			private set;
		}
	}
}
